package com.plant.es.test.config;

import org.apache.http.Header;
import org.apache.http.HttpHost;
import org.apache.http.auth.AuthScope;
import org.apache.http.auth.UsernamePasswordCredentials;
import org.apache.http.client.CredentialsProvider;
import org.apache.http.impl.client.BasicCredentialsProvider;
import org.apache.http.impl.nio.client.HttpAsyncClientBuilder;
import org.apache.http.impl.nio.reactor.IOReactorConfig;
import org.apache.http.message.BasicHeader;
import org.apache.http.ssl.SSLContextBuilder;
import org.apache.http.ssl.SSLContexts;
import org.elasticsearch.client.*;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

import javax.net.ssl.SSLContext;
import javax.swing.*;
import java.io.InputStream;
import java.nio.file.Files;
import java.security.KeyManagementException;
import java.security.KeyStore;
import java.security.KeyStoreException;
import java.security.NoSuchAlgorithmException;
import java.util.Iterator;

/**
 * @author shent
 */
@Configuration
public class RestClientConfig {
    @Value("${spring.elasticsearch.hostname}")
    private String hostname;
    @Value("${spring.elasticsearch.port}")
    private int port;

    @Bean
    public RestClient restClient() throws KeyStoreException, NoSuchAlgorithmException, KeyManagementException {
        // 如果有多个从节点可以持续在内部new多个HttpHost，参数1是IP，参数2是端口，参数3是通信协议
        RestClientBuilder clientBuilder = RestClient.builder(new HttpHost(hostname, port, "http"));

        // 1.设置请求头:设置Header编码
        Header[] defaultHeaders = {new BasicHeader("content-type", "application/json")};
        clientBuilder.setDefaultHeaders(defaultHeaders);
        // 添加其他配置，这些配置都是可选的，详情配置可看https://blog.csdn.net/jacksonary/article/details/82729556

        // 2.设置监听器，每次节点失败都可以监听到，可以作额外处理
//        clientBuilder.setFailureListener(new RestClient.FailureListener() {
//            @Override
//            public void onFailure(Node node) {
//                super.onFailure(node);
//                System.out.println(node.getName() + "==节点失败");
//            }
//        });

        /* 3.配置节点选择器，客户端以循环方式将每个请求发送到每一个配置的节点上，
        发送请求的节点，用于过滤客户端，将请求发送到这些客户端节点，默认向每个配置节点发送，
        这个配置通常是用户在启用嗅探时向专用主节点发送请求（即只有专用的主节点应该被HTTP请求命中）*/
        //clientBuilder.setNodeSelector(NodeSelector.SKIP_DEDICATED_MASTERS);

        //也可以自己定义节点选择器
//        clientBuilder.setNodeSelector(nodes -> {
        // 设置分配感知节点选择器，允许选择本地机架中的节点（如果有），否则转到任何机架中的任何其他节点。
//            boolean foundOne = false;
//            for (Node node : nodes) {
//                String rackId = node.getAttributes().get("rack_id").get(0);
//                if ("rack_one".equals(rackId)) {
//                    foundOne = true;
//                    break;
//                }
//            }
//            if (foundOne) {
//                Iterator<Node> nodeIt = nodes.iterator();
//                while (nodeIt.hasNext()) {
//                    Node node = nodeIt.next();
//                    String rackId = node.getAttributes().get("rack_id").get(0);
//                    if (!"rack_one".equals(rackId)) {
//                        nodeIt.remove();
//                    }}}});

        //4.配置HTTP异步请求ES的线程数
        /* 配置异步请求的线程数量，Apache Http Async Client默认启动一个调度程序线程，以及由连接管理器使用的许多工作线程
        （与本地检测到的处理器数量一样多，取决于Runtime.getRuntime().availableProcessors()返回的数量）。线程数可以修改如下,
        这里是修改为1个线程，即默认情况*/
//        clientBuilder.setHttpClientConfigCallback(httpAsyncClientBuilder ->
//                httpAsyncClientBuilder.setDefaultIOReactorConfig(
//                        IOReactorConfig.custom().setIoThreadCount(1).build()));

        //5.配置连接超时和套接字超时
//        clientBuilder.setRequestConfigCallback(requestConfigBuilder ->
//                requestConfigBuilder.setConnectTimeout(5000).setSocketTimeout(60000));

        //6.配置ES安全认证
        //如果ES设置了密码，那这里也提供了一个基本的认证机制，下面设置了ES需要基本身份验证的默认凭据提供程序
//        final CredentialsProvider credentialsProvider = new BasicCredentialsProvider();
//        credentialsProvider.setCredentials(AuthScope.ANY, new UsernamePasswordCredentials("user", "password"));
//        clientBuilder.setHttpClientConfigCallback(httpAsyncClientBuilder ->
//                httpAsyncClientBuilder.setDefaultCredentialsProvider(credentialsProvider));
        /*上面采用异步机制实现抢先认证，这个功能也可以禁用，这意味着每个请求都将在没有授权标头的情况下发送，然后查看它是否被接受，
        并且在收到HTTP 401响应后，它再使用基本认证头重新发送完全相同的请求，这个可能是基于安全、性能的考虑*/
//        clientBuilder.setHttpClientConfigCallback(httpAsyncClientBuilder -> {
//            httpAsyncClientBuilder.disableAuthCaching();
//            return httpAsyncClientBuilder.setDefaultCredentialsProvider(credentialsProvider);
//        });

        //7.配置通信加密，有多种方式：setSSLContext、setSSLSessionStrategy和setConnectionManager(它们的重要性逐渐递增)
//        KeyStore trustStore = KeyStore.getInstance("jks");
//        try (InputStream is = Files.newInputStream(keyStorePath)) {
//            trustStore.load(is, KeyStorePass.toCharArray);
//        }
//        SSLContextBuilder sslContextBuilder = SSLContexts.custom().loadTrustMaterial(trustStore, null);
//        final SSLContext sslContext = sslContextBuilder.build();
//        clientBuilder.setHttpClientConfigCallback(httpAsyncClientBuilder ->
//                httpAsyncClientBuilder.setSSLContext(sslContext));

        return clientBuilder.build();
    }

    @Bean
    public RestHighLevelClient restHighLevelClient() {
        // 如果有多个从节点可以持续在内部new多个HttpHost，参数1是IP，参数2是端口，参数3是通信协议
        return new RestHighLevelClient(RestClient.builder(new HttpHost(hostname, port, "http")));
    }

}
